package de.lighti.starcraft;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Logger;

import de.lighti.starcraft.model.Match;
import de.lighti.starcraft.model.StarcraftBot;
import de.lighti.starcraft.model.StarcraftMap;
import de.lighti.starcraft.util.IniParser;
import de.lighti.starcraft.util.UtilFunctions;

public class StatTool implements Runnable {
    private final static String SETTINGS_PATH = ".\\..\\Settings\\";

    private final static String BOTS_INI = SETTINGS_PATH + "Bots.ini";

    private final static String MAPS_INI = SETTINGS_PATH + "Maps.ini";
    private static final Logger LOGGER = Logger.getLogger( StatTool.class.getName() );

    private static Vector<StarcraftMap> maps;
    private static Vector<StarcraftBot> bots;
    private static Map<StarcraftBot, StarcraftBotStats> botStats;

    private static String[] args;

    private static void addBot( StarcraftBot b ) {
        if (!bots.contains( b )) {
            bots.add( b );
        }
        botStats.put( b, new StarcraftBotStats( b ) );
    }

    private static void countStats( Vector<Match> matches ) {
        for (final Match m : matches) {
            final StarcraftMap map = m.getMap();
            final StarcraftBot one = m.getHomeBot();
            final StarcraftBot two = m.getAwayBot();
            final StarcraftBotStats oneStats = botStats.get( one );
            final StarcraftBotStats twoStats = botStats.get( two );
            if (!m.isWasDraw()) {
                if (m.isHostwon()) {
                    oneStats.wonOver( two, map );
                }
                else {
                    twoStats.wonOver( one, map );
                }
            }
        }

    }

    public static void main( String[] args ) {
        StatTool.args = args;
        try {
            new StatTool().run();
        }
        catch (final Exception e) {
            LOGGER.severe( e.getLocalizedMessage() );
            e.printStackTrace();
        }

    }

    private static Match readMatch( String line ) {
        //We explicitly don't include " " as whitespace because names might
        //consist of two words. Instead, we call trim() on every token       
        final StringTokenizer tokens = new StringTokenizer( line, ",\t\n" );

        final Integer gameid = Integer.parseInt( tokens.nextToken().trim() );
        final Integer round = Integer.parseInt( tokens.nextToken().trim() );

        final String oneName = tokens.nextToken().trim();
        StarcraftBot one = null;
        for (final StarcraftBot b : bots) {
            if (b.getName().equals( oneName )) {
                one = b;
            }
        }
        if (one == null) {
            LOGGER.warning( "unknown bot name: " + oneName + ". Make sure it's correctly spelled in the Bots.ini." );
            one = new StarcraftBot( oneName, 0l, "<unknown>", false );
            LOGGER.warning( "Assuming bot is " + one );
            addBot( one );
        }

        final String twoName = tokens.nextToken().trim();
        StarcraftBot two = null;
        for (final StarcraftBot b : bots) {
            if (b.getName().equals( twoName )) {
                two = b;
            }
        }
        if (two == null) {
            LOGGER.warning( "unknown bot name: " + twoName + ". Make sure it's correctly spelled in the Bots.ini." );
            two = new StarcraftBot( twoName, 0l, "<unknown>", false );
            LOGGER.warning( "Assuming bot is " + two );
            addBot( two );

        }

        final String mapName = tokens.nextToken().trim();
        StarcraftMap map = null;
        for (final StarcraftMap m : maps) {
            if (m.getName().equals( mapName )) {
                map = m;
            }
        }
        if (map == null) {
            throw new IllegalStateException( "unknown map name: " + mapName );
        }

        final Match m = new Match( gameid, round, one, two, map );
        /** True if the host won the game */
        final boolean hostwon = Boolean.parseBoolean( tokens.nextToken().trim() );
        m.setHostwon( hostwon );

        final boolean hostcrash = Boolean.parseBoolean( tokens.nextToken().trim() );
        m.setHostcrash( hostcrash );
        ;

        /** True if the away bot crashed */
        final boolean awaycrash = Boolean.parseBoolean( tokens.nextToken().trim() );
        m.setAwaycrash( awaycrash );
        ;

        /** Number of times the host bot exceeds 55ms on a frame */
        final int hostTimer55ms = Integer.parseInt( tokens.nextToken().trim() );
        m.setHostTimer55ms( hostTimer55ms );

        /** Number of times the host bot exceeds 1sec on a frame */
        final int hostTimer1sec = Integer.parseInt( tokens.nextToken().trim() );
        m.setHostTimer1sec( hostTimer1sec );

        /** Number of times the host bot exceeds 10sec on a frame */
        final int hostTimer10sec = Integer.parseInt( tokens.nextToken().trim() );
        m.setHostTimer10sec( hostTimer10sec );

        /** Number of times the away bot exceeds 55ms on a frame */
        final int awayTimer55ms = Integer.parseInt( tokens.nextToken().trim() );
        m.setAwayTimer55ms( awayTimer55ms );

        /** Number of times the away bot exceeds 1sec on a frame */
        final int awayTimer1sec = Integer.parseInt( tokens.nextToken().trim() );
        m.setAwayTimer1sec( awayTimer1sec );

        /** Number of times the away bot exceeds 10sec on a frame */
        final int awayTimer10sec = Integer.parseInt( tokens.nextToken().trim() );
        m.setAwayTimer10sec( awayTimer10sec );

        /** True if this game was a draw forcing a determination based on score */
        final boolean wasDraw = Boolean.parseBoolean( tokens.nextToken().trim() );
        m.setWasDraw( wasDraw );

        /** The score the host bot aquired */
        final int hostScore = Integer.parseInt( tokens.nextToken().trim() );
        m.setHostScore( hostScore );

        /** The score the away bot aquired */
        final int awayScore = Integer.parseInt( tokens.nextToken().trim() );
        m.setAwayScore( awayScore );

        /** The final frame in this game. Only valid if the game didn't timeout */
        final int finalFrame = Integer.parseInt( tokens.nextToken().trim() );
        m.setFinalFrame( finalFrame );

        final long hostTime = Integer.parseInt( tokens.nextToken().trim() );
        m.setHostTime( hostTime );

        final long guestTime = Integer.parseInt( tokens.nextToken().trim() );
        m.setGuestTime( guestTime );

        LOGGER.fine( "Parsed match #" + m.getGameID() );
        return m;
    }

    private Set<StarcraftMap> parseMapArgs() {
        if (args.length < 2) {
            return new HashSet<StarcraftMap>( maps );
        }
        else {
            final HashSet<StarcraftMap> ret = new HashSet<StarcraftMap>();
            for (int i = 1; i < args.length; i++) {
                final String name = args[i];
                final StarcraftMap map = UtilFunctions.findMapInCollection( maps, name );
                if (map == null) {
                    LOGGER.warning( "Map " + name + " not found in Maps.ini. Are you sure it's spelled correctly?" );
                    ret.add( new StarcraftMap( name, null ) );
                }
                else {
                    ret.add( map );
                }
            }
            return ret;
        }
    }

    @Override
    public void run() {
        if (args == null || args.length == 0) {
            System.out.println( "usage: java -jar StatTool.jar <PathToRawData.txt> [<map_name_1> .. <map_name_n>]" );
            System.exit( -1 );
        }

        final File file = new File( args[0] );
        if (!file.canRead()) {
            LOGGER.severe( args[0] + " can not be read." );
            System.exit( -1 );
        }

        botStats = new HashMap<StarcraftBot, StarcraftBotStats>();

        BufferedReader reader = null;
        try {
            final Vector<Match> matches = new Vector<Match>();
            maps = IniParser.parseMaps( MAPS_INI );
            bots = IniParser.parseBots( BOTS_INI );
            for (final StarcraftBot b : bots) {
                if (!b.isBenchmarkBot()) {
                    addBot( b );
                }
            }
            reader = new BufferedReader( new FileReader( file ) );
            String line = null;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith( "#" )) {
                    continue;
                }

                matches.add( readMatch( line ) );
            }

            countStats( matches );
            LOGGER.info( "Parsed " + matches.size() + " matches." );

            for (final StarcraftBot b : botStats.keySet()) {
                if (b.isBenchmarkBot()) {
                    botStats.remove( b );
                }

            }
            final Set<StarcraftMap> maps = parseMapArgs();

            final Vector<StarcraftBotStats> winners = new Vector<StarcraftBotStats>( botStats.values() );
            Collections.sort( winners, new Comparator<StarcraftBotStats>() {

                @Override
                public int compare( StarcraftBotStats o1, StarcraftBotStats o2 ) {
                    if (o1.getTotalWins() > o2.getTotalWins( maps )) {
                        return -1;
                    }
                    else if (o1.getTotalWins() < o2.getTotalWins( maps )) {
                        return 1;
                    }
                    else {
                        return 0;
                    }
                }
            } );
            System.out.print( "The following scores are based on these maps:" );
            for (final StarcraftMap map : maps) {
                System.out.print( map.getName() + " " );
            }
            System.out.print( "\n" );

            System.out.println( "Placings: " );
            int i = 1;
            for (final StarcraftBotStats b : winners) {
                System.out.println( "#" + i + " " + b.getBot().getName() + "(" + b.getTotalWins() + ")" );
                i++;
            }

            System.out.println();
            System.out.println( "win matrix (\"Row win-rate over column\")" );
            System.out.print( "\t" );
            for (final StarcraftBotStats col : botStats.values()) {
                System.out.print( col.getBot().getName() + "\t" );
            }
            System.out.print( "\n" );

            for (final StarcraftBotStats row : botStats.values()) {
                System.out.print( row.getBot().getName() + "\t" );
                for (final StarcraftBotStats col : botStats.values()) {
                    if (col != row) {
                        System.out.print( String.format( "%.2f", row.winrate( col, maps ) ) + "\t" );
                    }
                }
                System.out.print( "\n" );
            }
        }
        catch (final Exception e) {
            throw new RuntimeException( e );
        }
        finally {
            if (reader != null) {
                try {
                    reader.close();
                }
                catch (final IOException e) {
                    //Die silently
                }
            }
        }

    }
}
